﻿/*
 * Overlay Adorner
 *
 * Derived from the below:  
 *   -Benjamin Gale's ModelContentPresenter
 *   -Prism's PopupActionWindow
 *   -Codeplex Edi/MsgBox: OverlayNotification
 */
using System;
using System.Collections;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;

namespace Hopscotch.Presentation.Common.Adorners
{
    public class OverlayAdorner : Adorner
    {
        /// <summary>The element to display on the adorner layer</summary>
        private FrameworkElement contentElement;
        private List<UIElement> logicalChildren;

        private KeyboardNavigationMode cachedTabNavigationMode;
        private KeyboardNavigationMode cachedDirectionalNavigationMode;
        private IInputElement cachedFocusedElement;

        private static readonly TraversalRequest traversalDirection
            = new TraversalRequest(FocusNavigationDirection.First);

        public event EventHandler Closed;

        public OverlayAdorner(FrameworkElement adornedElement, FrameworkElement element)
          : base(adornedElement)
        {
            this.contentElement = element;
        }

        public void AddOverlay()
        {
            base.AddLogicalChild(contentElement);
            base.AddVisualChild(contentElement);

            AdornerLayer adornerLayer = AdornerLayer.GetAdornerLayer(this.AdornedElement);

            // Let the overlay cover the target host element
            Overlay_SizeChanged(this.AdornedElement, null);
            (this.AdornedElement as FrameworkElement).SizeChanged += Overlay_SizeChanged;

            /*
             * Cache the keyboard navigation mode of the adorned element before setting it to
             * 'none' so that it can be restored when the overlay is removed
             */
            cachedTabNavigationMode = KeyboardNavigation.GetTabNavigation(this.AdornedElement);
            cachedDirectionalNavigationMode = KeyboardNavigation.GetDirectionalNavigation(this.AdornedElement);
            cachedFocusedElement = Keyboard.FocusedElement;

            KeyboardNavigation.SetTabNavigation(this.AdornedElement, KeyboardNavigationMode.None);
            KeyboardNavigation.SetDirectionalNavigation(this.AdornedElement, KeyboardNavigationMode.None);

            /*
             * Show the overlay (which in turn shows the modal content as it is a child of
             * the overlay) and move focus to the first logical element.
             */
            this.contentElement.MoveFocus(traversalDirection);

            adornerLayer.Add(this);
        }

        public void RemoveOverlay()
        {
            base.RemoveLogicalChild(contentElement);
            base.RemoveVisualChild(contentElement);

            /*
             * Restore the cached keyboard navigation value on the primary content and move
             * focus to its first logical element.
             */
            KeyboardNavigation.SetTabNavigation(this.AdornedElement, cachedTabNavigationMode);
            KeyboardNavigation.SetDirectionalNavigation(this.AdornedElement, cachedDirectionalNavigationMode);

            Keyboard.Focus(cachedFocusedElement);

            this.AdornedElement.MoveFocus(traversalDirection);

            AdornerLayer adornerLayer = AdornerLayer.GetAdornerLayer(this.AdornedElement);
            adornerLayer.Remove(this);

            (this.AdornedElement as FrameworkElement).SizeChanged -= Overlay_SizeChanged;
            RaiseClosed();
        }

        private void Overlay_SizeChanged(object sender, SizeChangedEventArgs e)
        {
            var uiTarget = sender as FrameworkElement;
            if (uiTarget != null)
            {
                this.Width = uiTarget.ActualWidth;
                this.Height = uiTarget.ActualHeight;
            }
        }

        private void RaiseClosed()
        {
            var handler = Closed;
            if (handler != null)
                handler.Invoke(this, new EventArgs());
        }

        protected override Size MeasureOverride(Size constraint)
        {
            contentElement.Measure(constraint);
            return contentElement.DesiredSize;
        }

        protected override Size ArrangeOverride(Size finalSize)
        {
            var location = new Point(0, 0);
            var rect = new Rect(location, finalSize);
            contentElement.Arrange(rect);

            return finalSize;
        }

        protected override int VisualChildrenCount
        {
            get { return 1; }
        }

        protected override Visual GetVisualChild(int index)
        {
            if (index != 0)
                throw new ArgumentOutOfRangeException(nameof(index));
            return contentElement;
        }

        protected override IEnumerator LogicalChildren
        {
            get
            {
                if (logicalChildren == null)
                {
                    logicalChildren = new List<UIElement>();
                    logicalChildren.Add(contentElement);
                }

                return logicalChildren.GetEnumerator();
            }
        }
    }
}
